#pragma once
#include <cerrno>
#include <vector>
#include <string>
#include <cstring>
#include <iostream>
#include <functional>
#include <unordered_map>
#include <pthread.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "error.hpp"
#include "Thread.hpp"
#include "lockGuard.hpp"
#include "circularQueue.hpp"

namespace ns_server
{
    const uint16_t default_port = 8888;
    const std::string default_ip = "192.168.20.8";
    using func = std::function<std::string(std::string)>;
    class UdpServer
    {
    public:
        // 云服务器和端口号不能够自己设定ip
        UdpServer(uint16_t port = default_port /*,std::string ip = default_ip*/) : _port(port) /*,_ip(ip)*/
        {
            pthread_mutex_init(&_lock, nullptr);
            // 要让func_t这个函数别名调用类内的成员函数是必须要与this指针进行绑定的！
            // 对于非静态成员在绑定的时候，还需要指名是哪个域！
            _p = new Thread(1, std::bind(&UdpServer::Recv, this));
            _c = new Thread(2, std::bind(&UdpServer::Send, this));
        }
        void Start()
        {
            // 创建套接字
            _socket = socket(AF_INET, SOCK_DGRAM, 0);
            // AF_INET：创建网络通信的套接字
            // SOCK_DGRAM：面向字节流
            if (_socket < 0)
            {
                // strerror：打印错误码对应的信息
                std::cerr << "create socket failed... " << strerror(errno) << std::endl;
                exit(SOCKET_ERR);
            }

            // 绑定端口号和ip
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local)); // 对local进行清0
            // 未来这些数据都是要在网络中传输的，因此都要转化为网络序列
            local.sin_port = htons(_port);
            local.sin_family = AF_INET; // 表明是网络通信还是本主机去通信

            // local.sin_addr.s_addr = inet_addr(_ip.c_str());  //inet_addr：将点分十进制的ip转化为4字节整数，并将它转化为网络序列
            // Cannot assign requested address 这样子去设ip会直接报这个错误，不能你自己去设置（虚拟机和云服务器上都会这样出错）
            // 得用真实的绑ip的方法去绑定

            local.sin_addr.s_addr = INADDR_ANY; // 表明让服务器，在启动的时候可以bind本主机上任意ip

            if (bind(_socket, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                std::cerr << "bind socket failed... " << strerror(errno) << std::endl;
                exit(BIND_ERR);
            }
            std::cerr << "bind socket success... " << std::endl;

            // udp初始化工作，只要进行创建socket，并绑定端口号，ip地址即可。

            // 开始工作即运行生产者消费者
            _p->run();
            _c->run();
        }

        void adduser(std::string &user, struct sockaddr_in &peer)
        {
            LockGuard lockguard(&_lock);
            auto it = _onlineusers.find(user);
            if (it != _onlineusers.end())
                return;
            _onlineusers.insert(std::pair<std::string, struct sockaddr_in>({user, peer}));
        }

        void Recv()
        {
            std::cout << "udp server start..." << std::endl;
            // 开始通信，抖音在晚上你也是可以访问的，正确的是任何时刻都可以被访问
            // 所以它肯定是一个死循环

            while (true)
            {
                // 从客户端中获取数据
                char buf[1024] = {0};
                struct sockaddr_in peer;      // peer 远端的意思
                socklen_t len = sizeof(peer); // 在调用recvfrom时，要先填充len，recvfrom执行完后，len才会被重置为实际的大小
                int n = recvfrom(_socket, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&peer, &len);
                if (n < 0)
                    continue;

                buf[n] = 0;
                uint16_t clientport = htons(peer.sin_port);
                std::string clientip = inet_ntoa(peer.sin_addr);
                // debug
                std::cout << "client " << clientip << "-" << clientport << "#" << buf << std::endl;
                // 将获取的数据进行业务处理
                // 1.保存远端信息
                std::string user = clientip;
                user += "- " + std::to_string(clientport);
                adduser(user, peer);
                // 2.将接收到的信息保存到环形队列中 -- 因为这是一个pc问题
                //  服务端中生产者不断的收消息，并将它们放到缓冲区中，消费者从缓冲区中获取消息，并将它们转发
                std::string buff = user + "#" + buf; // 标识一下信息来源
                _cq.push(buff);
            }
        }

        void Send()
        {
            // 未来呢，服务器一定是不断的发送数据给所有客户端的，所以一定是个死循环！！！
            while (true)
            {
                // 在收到的数据转发给所有连接到服务器的客户端上
                // 1.从缓冲区中获取数据
                std::string msg;
                _cq.pop(&msg);
                // 2.将onlinesusers转存到临时变量vector中,避免一边使用sendto,一边获取用户，因为用户态和内核态不断切换，耗费时间
                std::vector<struct sockaddr_in> users;
                {
                    LockGuard lockgurd(&_lock);
                    for (auto &pair : _onlineusers)
                    {
                        users.push_back(pair.second);
                    }
                }
                // 3.进行数据转发
                for (auto &user : users)
                {
                    sendto(_socket, msg.c_str(), msg.size(), 0, (struct sockaddr *)&user, sizeof(user));
                }
            }
        }
        ~UdpServer()
        {
            pthread_mutex_destroy(&_lock);
            _p->join();
            _c->join();
            delete _p;
            delete _c;

        }

    private:
        circularQueue<std::string> _cq;
        std::unordered_map<std::string, struct sockaddr_in> _onlineusers;
        uint16_t _port;
        // std::string _ip;   // 云服务器和端口号不能够自己设定ip
        int _socket; // 套接字文件描述符
        // 创建p,c线程
        Thread *_p;
        Thread *_c;
        // 那么在多线程的场景下，_onlineusers就成了临界资源
        // 所以要加锁进行保护
        pthread_mutex_t _lock;
    };
}